#include <os/driver.h>
#include <os/initcall.h>
#include <os/debug.h>
#include <lib/unistd.h>
#include <lib/stdio.h>
#include <sys/ioctl.h>
#include <sys/res.h>
#include <driver/serial.h>

static uint8_t SerialRecv(device_extension_t *extension)
{
    while (!(In8(extension->line_status_reg) & LINE_STATUS_DATA_READY))
        ;
    return In8(extension->data_reg);
}

static void SerialSend(device_extension_t *extension, uint8_t data)
{
    while (!(In8(extension->line_status_reg) & LINE_STATUS_EMPTY_TRANSMITTER_HOLDING))
        ;
    return Out8(extension->data_reg, data);
}

static void SerialSetMode(device_extension_t *extension, uint8_t word, uint8_t stop, uint8_t parity)
{
    uint32_t word_len[WORD_MAX_NUM] = {LINE_WORD_LEN_5, LINE_WORD_LEN_6, LINE_WORD_LEN_7, LINE_WORD_LEN_8};
    uint32_t stop_bits[STOP_MAX_NUM] = {LINE_STOP_BIT_1, LINE_STOP_BIT_2};
    uint32_t parity_bits[PARITY_MAX_NUM] = {LINE_PARITY_NO, LINE_PARITY_ODD, LINE_PARITY_EVEN, LINE_PARITY_MARK, LINE_PARITY_SPACE};

    Out8(extension->line_ctrl_reg, word_len[word] | stop_bits[stop] | parity_bits[parity]); // write mode to reg

    extension->word = word;
    extension->stop = stop;
    extension->parity = parity;
}

static void SerialSetBaud(device_extension_t *extension, uint8_t baud)
{
    uint32_t baud_divisor[BAUD_MAX_NUM] = {2304, 1047, 524, 384, 192, 96, 48, 24, 12, 6, 3, 2, 1};
    uint32_t divisor = baud_divisor[baud];
    uint8_t temp = In8(extension->line_ctrl_reg); // store old data

    Out8(extension->line_ctrl_reg, LINE_DLAB | temp); // write back set DLAB and old data
    Out8(extension->divisor_low_reg, (uint8_t)divisor);
    Out8(extension->divisor_high_reg, (uint8_t)(divisor >> 8));
    Out8(extension->line_ctrl_reg, temp); // restore old data and clear DLAB

    extension->baud = baud;
}

static iostatus_t SerialOpen(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_SUCCESS;

    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return IO_SUCCESS;
}

static iostatus_t SerialClose(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_SUCCESS;

    ioreq->io_status.status = IO_SUCCESS;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return IO_SUCCESS;
}

static iostatus_t SerialRead(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    uint8_t *buff = (uint8_t *)ioreq->user_buff;
    int len = ioreq->parame.read.len;
    int size = 0;

    while (len--)
    {
        *buff-- = SerialRecv(extension);
        size++;
    }
    ioreq->io_status.status = IO_SUCCESS;
    ioreq->io_status.info = size;
    IoCompleteRequest(ioreq);
    return IO_SUCCESS;
}

static iostatus_t SerialWrite(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    uint8_t *buff = ioreq->user_buff;
    int len = ioreq->parame.write.len;
    int size;

    while (len--)
    {
        SerialSend(extension, *buff++);
        size++;
    }
    ioreq->io_status.status = IO_SUCCESS;
    ioreq->io_status.info = size;
    IoCompleteRequest(ioreq);
    return IO_SUCCESS;
}

static iostatus_t SerialDevCtl(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    uint32_t code = ioreq->parame.devctl.code;
    uint32_t arg = ioreq->parame.devctl.arg;
    iostatus_t status = IO_SUCCESS;

    switch (code)
    {
    case SERIALIO_SETBAUD:
        SerialSetBaud(extension, (uint8_t)arg);
        break;
    case SERIALIO_GETBAUD:
        *(uint32_t *)arg = extension->baud;
        break;
    case SERIALIO_SETMODE:
        SerialSetMode(extension, (uint8_t)arg & 0xf, (uint8_t)((arg >> 4) & 0xf), (uint8_t)((arg >> 8) & 0xf));
        break;
    case SERIALIO_GETMODE:
        *(uint32_t *)arg = (uint32_t)(extension->parity & 0xf) << 8 | (extension->stop & 0xf) << 4 | extension->word & 0xf;
        break;
    default:
        status = IO_FAILED;
        break;
    }
    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t SerialEnter(driver_object_t *driver)
{
    iostatus_t status = IO_SUCCESS;
    device_object_t *device;
    device_extension_t *extension;
    uint32_t iobase, irq;
    int i;
    char devname[DEVICE_NAME_LEN+1];

    for (i = 0; i < COM_NUM_MAX; i++)
    {
        status = IoCreateDevice(driver, sizeof(device_extension_t), DEVICE_NAME, DEVICE_TYPE_SERIAL, &device);
        if (status != IO_SUCCESS)
            return IO_FAILED;

        // buffer io mode
        device->flags = 0;
        extension = device->device_extension;
        sprintf(devname, "%s%d", DEVICE_NAME, i);
        strncpy(extension->device_name, devname, DEVICE_NAME_LEN);

        // select serial iobase
        switch (i)
        {
        case COM1:
            iobase = COM1_BASE;
            break;
        case COM2:
            iobase = COM2_BASE;
            break;
        case COM3:
            iobase = COM3_BASE;
            break;
        case COM4:
            iobase = COM4_BASE;
            break;
        }

        // select serial irq
        switch (i)
        {
        // COM1 and COM3 use irq4
        case COM1:
        case COM3:
            irq = IRQ4_SERIAL1;
            break;
        // COM2 and COM4 use irq3
        case COM2:
        case COM4:
            irq = IRQ3_SERIAL2;
            break;
        }
        // init serial io base
        extension->iobase = iobase;
        extension->data_reg = iobase + 0;
        extension->divisor_low_reg = iobase + 0;
        extension->int_enable_reg = iobase + 1;
        extension->divisor_high_reg = iobase + 1;
        extension->int_identify_reg = iobase + 2;
        extension->fifo_reg = iobase + 2;
        extension->line_ctrl_reg = iobase + 3;
        extension->modem_ctrl_reg = iobase + 4;
        extension->line_status_reg = iobase + 5;
        extension->modem_status_reg = iobase + 6;
        extension->scratch_reg = iobase + 7;

        extension->irq = irq;

        // set baud value
        Out8(extension->line_ctrl_reg, LINE_DLAB);
        Out8(extension->divisor_low_reg, (uint8_t)DEFAULT_DIVISION_VALUE);
        Out8(extension->divisor_high_reg, (uint8_t)(DEFAULT_DIVISION_VALUE >> 8));
        Out8(extension->line_ctrl_reg, 0);

        // set word,stop bit and no parity
        Out8(extension->line_ctrl_reg, LINE_WORD_LEN_8 | LINE_STOP_BIT_1 | LINE_PARITY_NO);

        // disable interrupt
        Out8(extension->int_enable_reg, 0);

        // enable fifo,clear buffer
        Out8(extension->fifo_reg, FIFO_ENABLE | FIFO_CLEAR_TRANSMIT | FIFO_CLEAR_RECEIVE | FIFO_ENABLE_64 | FIFO_TRIGGER_14);

        Out8(extension->scratch_reg, 0x00);

        // test serial
        Out8(extension->modem_ctrl_reg, MOMED_LOOKBACK_MODE);
        while (!In8(extension->line_status_reg) & LINE_STATUS_EMPTY_DARA_HOLDING)
            ;
        Out8(extension->data_reg, 0xAE);
        while (!In8(extension->line_status_reg) & LINE_STATUS_DATA_READY)
            ;
        if (In8(extension->data_reg) != 0xAE)
        {
            KPrint("[serial] %s: device %s test failed!\n", __func__, devname);
            IoDeleteDevice(device);
            status = IO_FAILED;
        }
        else
        {
            KPrint("[serial] %s: device %s test ok!\n", __func__, devname);
            status = IO_SUCCESS;
        }
        Out8(extension->modem_ctrl_reg, 0);
    }
    return status;
}

static iostatus_t SerialExit(driver_object_t *driver)
{
    device_object_t *device, *next;

    list_traversal_all_owner_to_next_safe(device, next, &driver->device_list, list)
    {
        list_del_init(&device->list);
    }

    // delete driver name
    string_del(&driver->name);
    return IO_FAILED;
}

static iostatus_t SerialDriverFunc(driver_object_t *driver)
{
    driver->driver_enter = SerialEnter;
    driver->driver_exit = SerialExit;

    driver->dispatch_fun[IOREQ_OPEN] = SerialOpen;
    driver->dispatch_fun[IOREQ_OPEN] = SerialClose;
    driver->dispatch_fun[IOREQ_WRITE] = SerialRead;
    driver->dispatch_fun[IOREQ_READ] = SerialWrite;
    driver->dispatch_fun[IOREQ_DEVCTL] = SerialDevCtl;

    // driver name
    string_new(&driver->name, DRIVER_NAME, DRIVER_NAME_LEN);
    return IO_SUCCESS;
}

static __init void SerialDriverEntry()
{
    KPrint("[driver] create serial driver\n");
    if (DriverObjectCreate(SerialDriverFunc) < 0)
    {
        KPrint("[driver] %s: serial driver create failed!\n", __func__);
    }
}

driver_initcall(SerialDriverEntry);